﻿
using Core.UserScript;
using Microsoft.CSharp;
using Service.src.Flow.JavaScript;
using Service.src.Model.Folw;
using Service.src.NW.Enum;
using Service.src.NW.Model.Folw;
using Service.src.NW.Model.Folw.Logic;
using Service.src.NW.Processor.Core;
using System;
using System.CodeDom.Compiler;
using System.Reflection;
using System.Text;
using Service.src.NW.Model.Enum;
using BaseUtils.Logger;
using System.Collections.Concurrent;
using BaseUtils.Helper;

namespace Service.src.NW.Base
{

    /// <summary>
    /// 脚本编译器
    /// </summary>
    public class ScriptCompiler
    {
        static CompilerParameters parameters;
        static CSharpCodeProvider provider = new CSharpCodeProvider();

        private static ConcurrentDictionary<string, CompilerResults> CompiledCodeCache = new ConcurrentDictionary<string, CompilerResults>();

        public ScriptCompiler()
        {
            parameters = new CompilerParameters
            {
                GenerateExecutable = false,
                GenerateInMemory = true,
                ReferencedAssemblies = { "System.dll", "System.Windows.Forms.dll","Microsoft.VisualBasic.dll",
                    "BaseUtils.dll", "Microsoft.CSharp.dll", "System.Core.dll" , "DatabaseCore.dll" ,
                 Assembly.GetExecutingAssembly().Location} // 添加必要的引用
            };
        }

        private readonly static object lockObject = new object();

        /// <summary>
        /// 编译脚本
        /// </summary>
        /// <param name="Code"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>
        public static Assembly CompileCode(string Code, NodeProcessor Node)
        {
            lock (lockObject)
            {
                try
                {
                    if (Node.IsPreCompiling)
                    {
                        CompilerResults DC;
                        CompiledCodeCache.TryRemove(Code, out DC);
                    }
                    if (CompiledCodeCache.ContainsKey(Code) && CompiledCodeCache.TryGetValue(Code, out CompilerResults c))
                    {
                        Assembly a = c.CompiledAssembly;
                        if (a != null)
                        {
                            return a;
                        }
                    }
                }
                catch (Exception ex)
                {
                    CompiledCodeCache.TryRemove(Code, out CompilerResults re);
                }
                try
                {

                    CompilerResults results = provider.CompileAssemblyFromSource(parameters, Code);

                    StringBuilder Errors = new StringBuilder();
                    if (results.Errors.HasErrors)
                    {
                        foreach (CompilerError error in results.Errors)
                        {
                            Errors.Append($"Error ({error.Line},{error.Column}): {error.ErrorText}\n");
                        }
                        Node.NodeStatus = NodeStatusEnum.ERROR;
                    }
                    if (Node.IsPreCompiling)
                    {
                        BaseProcessor.SendNodeLoggerError(Node,$"脚本编译错误，原因:{Errors.ToString()}");
                        JavaScriptEvent.Send(JavaScriptEventEnum.NODE_SCRIPT_COMPILE_ERROR, Errors);
                    }
                    CompiledCodeCache[Code] = results;
                    return results.CompiledAssembly;
                }
                catch (Exception ex)
                {
                    LoggerService.Error($"脚本编译异常code:{Code}", ex);
                    CompiledCodeCache.TryRemove(Code, out CompilerResults re);
                }
                return null;
            }
        }

        /// <summary>
        /// 完善脚本，得到最终运行脚本代码
        /// </summary>
        /// <param name="Node"></param>
        private void GeneratedScriptCode(NodeProcessor Node)
        {
            NodeInputParam Input = Node.NodeInputParam;
            if (Input == null || Input.NodeLogic == null)
            {
                Node.Code = (int)RuntimeErrorCodeEnum.NODE_INPUT_PARAM_NULL;
                Node.Logger = new NodeLogger($"{Node.id}节点脚本输入参数异常");
                Node.NodeStatus = 0;
                JavaScriptEvent.Send(JavaScriptEventEnum.NODE_LOGGER, Node);
                throw new Exception("节点脚本输入参数异常");
            }
            UserScript Script = Input.NodeLogic.Script;
            if (Script == null)
            {
                Node.Code = (int)RuntimeErrorCodeEnum.NODE_INPUT_PARAM_NULL;
                Node.Logger = new NodeLogger($"{Node.id}节点脚本输入参数异常");
                Node.NodeStatus = 0;
                JavaScriptEvent.Send(JavaScriptEventEnum.NODE_LOGGER, Node);
                return;
            }
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.AppendLine(Script.ScriptCode);
            stringBuilder.AppendLine("public partial class RuntimeScript:NodeBaseScript");
            stringBuilder.AppendLine("{");
            Script?.OutParams.ForEach(o =>
            {
                if (!string.IsNullOrEmpty(o.ValueType) && !string.IsNullOrEmpty(o.ParamName))
                {
                    stringBuilder.AppendLine($"public {o.ValueType} {o.ParamName};");
                }
            });
            Script?.InputParams.ForEach(i =>
            {
                if (!string.IsNullOrEmpty(i.ValueType) && !string.IsNullOrEmpty(i.TargetParamName))
                {
                    stringBuilder.AppendLine($"public {i.ValueType} {i.TargetParamName};");
                }
            });

            stringBuilder.AppendLine("}");
            Script.RunScriptCode = stringBuilder.ToString();
        }

        /// <summary>
        /// 运行脚本
        /// 
        /// </summary>
        /// <param name="ScriptCode"></param>
        public void RunScript(NodeProcessor Node)
        {
            Node.NodeStatus = NodeStatusEnum.ERROR;
            try
            {
                GeneratedScriptCode(Node);
                string RunScriptCode = Node.NodeInputParam?.NodeLogic?.Script?.RunScriptCode ?? null;
                if (RunScriptCode == null)
                {
                    return;
                }

                Assembly assembly = CompileCode(RunScriptCode, Node);

                if (assembly == null)
                {
                    BaseProcessor.SendNodeLoggerError(Node, $"脚本编辑异常，请检查脚本程序");
                    return;
                }
                Type type = assembly.GetType(NodeBaseScript.SCRIPT_CLASS_NAME);
                object instance = Activator.CreateInstance(type);

                SetScriptInputParamValue(Node, assembly.GetType(NodeBaseScript.SCRIPT_CLASS_NAME), instance);
                MethodInfo processMethod = type.GetMethod(NodeBaseScript.SCRIPT_METHODS_NAME_ASPECT);
                bool result = (bool)processMethod.Invoke(instance, null);

                SetScriptOutParamValue(Node, type, instance);
                Node.NodeStatus = result ? NodeStatusEnum.SUCCESS : NodeStatusEnum.ERROR;
            }
            catch (Exception ex)
            {
                Node.NodeStatus = 0;
                BaseProcessor.SendNodeLoggerError(Node, $"脚本运行失败：{ex.Message},{ex.InnerException}", RuntimeErrorCodeEnum.NODE_SCRIPT_RUN_ERROR);
            }
        }

        /// <summary>
        /// 给脚本的入参赋值
        /// </summary>
        /// <param name="node"></param>
        /// <param name="type"></param>
        /// <param name="instance"></param>
        private void SetScriptInputParamValue(NodeProcessor node, Type type, object instance)
        {
          
            node.NodeInputParam.NodeLogic.Script.InputParams.ForEach(_InputParam =>
            {
                if (node.id == 2130)
                {
                   // Console.WriteLine("@");
                }
                if (!string.IsNullOrEmpty(_InputParam.ValueType) && !string.IsNullOrEmpty(_InputParam.TargetParamName))
                {
                    int DID = _InputParam.DataSourceId;
                    NodeDataSource nds = BaseProcessor.GetNodeInputParam(node, _InputParam);
                    if (nds==null)
                    {
                        string InputJson = ConvertHelper.ToJson(_InputParam);
                        BaseProcessor.SendNodeLoggerError(node,$"设置节点参数异常,{InputJson}");
                        return;
                    }
                    FieldInfo field = type.GetField(_InputParam.TargetParamName);
                 
                    if (field == null)
                    {
                        BaseProcessor.SendNodeLoggerError(node, $"设置节点参数异常");
                        return;
                    }
                    switch (_InputParam.ValueType)
                    {
                        case "float":
                            float fv = 0;
                            float.TryParse(nds.Value + "", out fv);
                            field.SetValue(instance, fv);
                            break;
                        case "int":
                            int iv = 0;
                            int.TryParse(nds.Value + "", out iv);
                            field.SetValue(instance, iv);
                            break;
                        case "bool":
                            bool bv = false;
                            bool.TryParse(nds.Value + "", out bv);
                            field.SetValue(instance, bv);
                            break;
                        case "long":
                            long lv = 0;
                            long.TryParse(nds.Value + "", out lv);
                            field.SetValue(instance, lv);
                            break;
                        case "string":
                            field.SetValue(instance, nds.Value);
                            break;
                        default:
                            field.SetValue(instance, nds.Value);
                            break;
                    }
                }
            });
        }

        //

        private decimal cv(decimal v, string type)
        {
            decimal _v = v;


            return _v;
        }

        /// <summary>
        /// 给脚本的输出参数赋值
        /// </summary>
        /// <param name="node"></param>
        /// <param name="type"></param>
        /// <exception cref="NotImplementedException"></exception>
        private void SetScriptOutParamValue(NodeProcessor Node, Type type, object instance)
        {
            Node.NodeInputParam.NodeLogic.Script.OutParams.ForEach(p =>
            {
                if (Node.id == 2130)
                {
                    // Console.WriteLine("@");
                }
                FieldInfo field = type.GetField(p.ParamName);
                if (field == null)
                {
                    JavaScriptEvent.Send(JavaScriptEventEnum.NODE_ERROR, Node);
                }
                else
                {
                    dynamic value = field.GetValue(instance);
                    p.Value = value;

                    Node.NodeOutParam.SetOutParamValue(p.ParamName, value);
                }
            });
        }
    }
}
